<template>
  <div
    class="map_wrapper"
    :style="{ width: !isFull && circleParams ? 'calc(100% - 50%)' : '100%' }"
  >
    <div ref="mapRef" class="container"></div>
    <div class="btn_box">
      <div
        @click="checkMouseState('drag')"
        class="drag_btn btn"
        :class="{ active_btn: btnState === 'drag' }"
      >
        拖拽模式
      </div>
      <div
        @click="checkMouseState('circle_draw')"
        class="circle_draw_btn btn"
        :class="{ active_btn: btnState === 'circle_draw' }"
      >
        圈选模式
      </div>
      <div
        @click="checkMouseState('polygon_draw')"
        class="btn polygon_draw_btn"
        :class="{ active_btn: btnState === 'polygon_draw' }"
      >
        <n-button disabled :bordered="false">手动标记</n-button>
      </div>
    </div>
    <div v-show="circleParams" class="expend_btn" @click="isFull = !isFull">
      <n-icon size="18" :component="isFull ? CaretBackOutline : CaretForward" />
    </div>
    <n-input
      v-model:value="companyName"
      @focus="searchFocus"
      @blur="searchBlur"
      @update:value="updateCompanyName"
      :maxlength="20"
      clearable
      laceholder="搜索"
      class="search_input"
    >
      <template #suffix>
        <n-icon :component="SearchOutline" />
      </template>
    </n-input>
    <div class="list_wrapper" v-show="listShow">
      <div class="content_wrapper" v-if="list.length">
        <div class="l_item" v-for="item in list" :key="item.id" @click="drawPoint(item)">
          <div class="l_title">{{ item.companyName }}</div>
          <div class="l_address">{{ item.regAddress }}</div>
        </div>
      </div>
      <span v-else class="empty_text">暂无数据</span>
    </div>
  </div>
</template>

<script setup lang="ts">
import { SearchOutline } from '@vicons/ionicons5'
import { DrawScene, PolygonDraw, CircleDraw, OperateEventType, ActionStatus } from 'bmap-draw'
import { BtnStatus, Data } from '../types'
import { centerPointAddress, circleParams, currentDatas, isFull } from '../model'
import {
  postCompanyCompanyInfoCircleSearch,
  postCompanyCompanyInfoPageList
} from '@/api/views_bMap'
import {
  postCompanyCompanyInfoCircleSearchResultType as T,
  postCompanyCompanyInfoPageListParamsType as P
} from '@/api/types/views_bMap'

import { CaretBackOutline, CaretForward } from '@vicons/ionicons5'
import { data1, data2 } from '../mock'

/** 关键字 */
const companyName = ref<string>()
/** 列表显隐 */
const listShow = ref(false)
/** 搜索列表 */
const list = ref<Data>([])
/**
 * @description 搜索框聚焦状态
 */
function searchFocus() {
  // 1.清空地图，关闭右侧表格
  scene.value.clearData()
  clearMarker()
  cz.value = undefined
  circleParams.value = undefined
  checkMouseState('drag')
  // 2.打开搜索框
  listShow.value = true
  updateCompanyName()
}
/**
 * @description 搜索框失焦状态
 */
function searchBlur() {
  setTimeout(() => {
    listShow.value = false
  }, 500)
}
/**
 * @description 绘制企业点位
 */
function drawPoint(k: Data[number]) {
  clearMarker()
  const point = new window.BMapGL.Point(k.lngLat.lon, k.lngLat.lat)
  // 创建点标记
  const marker = new window.BMapGL.Marker(point)
  // 将点位信息挂载到实例上
  marker._data = k
  mapInstance.value.addOverlay(marker)

  // 创建信息窗口
  const opts = {
    width: 300,
    // height: 40,
    title: k.companyName
  }
  const infoWindow = new window.BMapGL.InfoWindow(
    `<span>地址：${k.regAddress}
    <span>`,
    opts
  )
  // 点标记添加点击事件
  marker.addEventListener('click', function () {
    mapInstance.value.openInfoWindow(infoWindow, point) // 开启信息窗口
  })
  markers.value.push(marker)
  const curCZ = mapInstance.value.getViewport([point])
  curCZ.zoom -= 1
  mapInstance.value.setViewport(curCZ)
}
/**
 * @description 更新搜索关键字
 */
function updateCompanyName() {
  list.value = []
  // 根据当前地图中心点查取地理信息
  const point = mapInstance.value.getCenter()
  const myGeo = new window.BMapGL.Geocoder()
  myGeo.getLocation(point, async function (result: any) {
    if (result) {
      const {
        addressComponents: { city, province }
      } = result

      const params: P = {}
      const address = (document.getElementsByClassName('ui_city_change_inner')[0] as HTMLElement)
        .innerText

      // 根据控件所获得的位置信息对应接口字段
      switch (address) {
        case city:
          params.addressCity = address
          break
        case province:
          params.addressProvince = address
          break
        default:
          break
      }
      // const { data } = await postCompanyCompanyInfoPageList({
      //   ...params,
      //   companyName: companyName.value,
      //   pageSize: 10
      // })
      // list.value = data.list || []
      list.value = data1
    }
  })
}

/** 地图容器 */
const mapRef = ref<HTMLDivElement>()
/** 地图实例 */
const mapInstance = shallowRef<any>()
/** 按钮状态 */
const btnState = ref<BtnStatus>('drag')
/** 圆绘制器 */
const circleController = ref<any>(null)
/** 多边形绘制器 */
const polygonController = ref<any>(null)
/** 场景 */
const scene = ref<any>(null)
/** 点标记 */
const markers = ref<any[]>([])
/** 绘制的图形形成的覆盖物集合，用于自适应视野使用 */
const cz = ref<any>()

/**
 * @description 清除点标记
 */
function clearMarker() {
  markers.value?.forEach((k) => {
    mapInstance.value.removeOverlay(k)
  })
  markers.value = []
}

/**
 * @description 切换鼠标绘制模式（普通拖拽、绘制圆、绘制多边形）
 * @param state 按钮状态
 */
function checkMouseState(state: BtnStatus) {
  btnState.value = state
  switch (state) {
    case 'drag':
      // 1、关闭圆绘制模式
      circleController.value.close()
      // 2、关闭多边形绘制模式
      polygonController.value.close()
      break
    case 'circle_draw':
      // 1、清除地图上之前绘制的覆盖物
      scene.value.clearData()
      clearMarker()
      cz.value = undefined
      // 2、关闭多边形绘制模式
      polygonController.value.close()
      // 3、开启圆绘制模式
      circleController.value.open()
      break
    case 'polygon_draw':
      // 1、清除地图上之前绘制的覆盖物
      scene.value.clearData()
      clearMarker()
      cz.value = undefined
      // 2、关闭圆绘制模式
      circleController.value.close()
      // 3、开启多边形绘制模式
      polygonController.value.open()
      break
    default:
      break
  }
}

/**
 * @description 获取在圆内的企业点位
 * @param distance 距离/KM
 * @param lat 维度
 * @param lon 经度
 */
async function getCompanyList(distance: number, lat: number, lon: number) {
  // 设置数字图标
  const generateLabel = (point: any, index: number) => {
    // 序号
    const order = index + 1
    // 文字偏移，若是两位数则偏移一个fontSize， 一位数则偏移半个, 后面加的数字是因为不是等宽字体，数字看起来视觉有些偏差，自己调的
    const offsetX = order >= 10 ? -12 + 3 : -6 + 1
    const size = new window.BMapGL.Size(offsetX, -24)
    const label = new window.BMapGL.Label(order, { position: point, offset: size })
    label.setStyle({
      color: '#fff',
      fontSize: '12px',
      border: 'none',
      padding: 0,
      backgroundColor: 'transparent'
    })
    return label
  }

  // 翻页的情况下，直接更新数字图标
  if (markers.value.length) {
    // 1.先清空上一批的数字图标
    markers.value.forEach((k) => {
      const label = k.getLabel()
      if (label) {
        label.setContent('')
      }
    })
    // 2.设置当前页的数字图标
    currentDatas.value.forEach((k, index) => {
      // 找到对应的marker
      const markersIndex = markers.value.findIndex((e) => e._data.companyName === k.companyName)
      const point = new window.BMapGL.Point(k.lngLat.lon, k.lngLat.lat)
      const label = generateLabel(point, index)
      markers.value[markersIndex].setLabel(label)
    })
    return
  }

  // const { data } = await postCompanyCompanyInfoCircleSearch({ distance, lat, lon })
  markers.value = []
  const data = data2
  data?.forEach((k) => {
    const point = new window.BMapGL.Point(k.lngLat.lon, k.lngLat.lat)
    // 创建点标记
    const marker = new window.BMapGL.Marker(point)
    // 将点位信息挂载到实例上
    marker._data = k
    mapInstance.value.addOverlay(marker)

    // 创建信息窗口
    const opts = {
      width: 300,
      // height: 40,
      title: k.companyName
    }
    const infoWindow = new window.BMapGL.InfoWindow(`地址：${'--'}`, opts)

    // 点标记添加点击事件
    marker.addEventListener('click', function () {
      mapInstance.value.openInfoWindow(infoWindow, point) // 开启信息窗口
    })

    // 如果是表格当前页的数据，则加入数字标签
    const index = currentDatas.value.findIndex((e) => e.companyName === k.companyName)
    if (index > -1) {
      const label = generateLabel(point, index)
      marker.setLabel(label)
    }

    markers.value.push(marker)
  })
}

/**
 * @description 根据鼠标绘制的图形获取在图形内的点位
 * @param overlay 鼠标绘制的图形
 */
function getPoints(overlay: any) {
  const { radius, latLng, points } = overlay

  // 根据绘制的圆点坐标得到地址描述,用于右侧展示信息
  const point = new window.BMapGL.Point(latLng.lng, latLng.lat)
  const myGeo = new window.BMapGL.Geocoder()
  myGeo.getLocation(point, function (result: any) {
    if (result) {
      centerPointAddress.value = result
    }
  })

  // 多边形的节点组成的Point[]
  let newPoints: any = null
  // 圆形绘制器的半径
  let distance: number = 0

  switch (btnState.value) {
    // 圆形绘制，需要获取中心点和半径
    case 'circle_draw':
      cz.value = mapInstance.value.getViewport(overlay.getBounds())
      distance = radius / 1000
      // 更新圆形参数，通知右边表格更新数据,数据更新后，会触发监听器，调用getCompanyList绘制所有点位
      if (radius / 1000 > 20) {
        distance = 20
        window.$message('warning', '绘制的范围过大，将为您查询出周围20km内的数据')
      }
      circleParams.value = { distance, lat: latLng.lat, lon: latLng.lng }
      break

    // 多变形绘制，需要获取组成多边形的节点
    case 'polygon_draw':
      newPoints = points.map((k: any) => new window.BMapGL.Point(k.latLng.lng, k.latLng.lat))
      cz.value = mapInstance.value.getViewport(newPoints)
      break
    default:
      break
  }

  //自适应视野大小
  cz.value.zoom -= 1
  mapInstance.value.setViewport(cz.value)
}

/**
 * @description 生成地图绘制器
 */
function generateMapDrawer() {
  // 1.生成地图
  mapInstance.value = new window.BMapGL.Map(mapRef.value)
  mapInstance.value.centerAndZoom(new window.BMapGL.Point(116.41248, 39.927893), 13)
  mapInstance.value.enableScrollWheelZoom()

  // 1.1创建城市选择控件
  const cityControl = new window.BMapGL.CityListControl({
    // 控件的停靠位置（可选，默认左上角）
    anchor: window.BMAP_ANCHOR_TOP_RIGHT,
    // 控件基于停靠位置的偏移量（可选）
    offset: new window.BMapGL.Size(10, 5)
  })
  // 将控件添加到地图上
  mapInstance.value.addControl(cityControl)

  // 2.初始化场景
  scene.value = new DrawScene(mapInstance.value, {
    noLimit: true
  })

  // 3.初始化圆绘制器
  circleController.value = new CircleDraw(scene.value, {
    isOpen: false,
    isSeries: false, // 是否开启批量绘制
    labelOptions: {
      borderRadius: '2px',
      background: '#ff',
      border: '0px solid #ff',
      color: '#595959',
      fontSize: '12px',
      letterSpacing: '0',
      padding: '5px'
    },
    hideTip: false,
    baseOpts: {
      strokeColor: '#0779FF',
      fillColor: '#0779FF',
      strokeWeight: 2,
      strokeOpacity: 1,
      fillOpacity: 0.2
    }
  })

  // 34初始化多边形绘制器
  polygonController.value = new PolygonDraw(scene.value, {
    hideTip: false, // 隐藏绘制提示
    isSeries: false, // 是否开启批量绘制
    isOpen: false,
    labelOptions: {
      borderRadius: '2px',
      background: '#b5d3fb',
      border: '1px solid #b5d3fb',
      color: '#333',
      fontSize: '12px',
      letterSpacing: '0',
      padding: '5px'
    },
    baseOpts: {
      fillColor: '#fff',
      strokeWeight: 5,
      strokeOpacity: 1,
      fillOpacity: 0.2
    }
  })

  // 4.监听场景完成事件
  scene.value.addEventListener(OperateEventType.COMPLETE, (e: any) => {
    switch (e.target.from) {
      // 绘制图形成功
      case ActionStatus.DRAW_ON_MAP:
        getPoints(e.target.overlay)
        checkMouseState('drag')
        break
      default:
        break
    }
  })

  // 4.监听场景取消事件
  scene.value.addEventListener(OperateEventType.CANCEL, (e: any) => {
    switch (e.target.from) {
      case ActionStatus.DRAW_ON_MAP_CANCEL:
        checkMouseState(btnState.value)
        break
      default:
        break
    }
  })
}

onMounted(() => {
  generateMapDrawer()
})

watch(
  () => isFull.value,
  () => {
    setTimeout(() => {
      cz.value && mapInstance.value.setViewport(cz.value)
    }, 100)
  }
)

watch(
  () => currentDatas.value,
  (val) => {
    if (val.length && circleParams.value) {
      const { distance, lat, lon } = circleParams.value as {
        distance: number
        lat: number
        lon: number
      }
      getCompanyList(distance, lat, lon)
    }
  },
  { deep: true }
)
</script>

<style lang="less" scoped>
.map_wrapper {
  position: relative;
  height: 100%;
  :deep(.city_content_top) {
    height: 46px;
  }
  :deep(
      .ui_city_change_click .ui_city_change_inner i,
      .ui_city_change_click_close .ui_city_change_inner i
    ) {
    top: 17px;
  }
  :deep(.BMap_CityListCtrl) {
    top: 16px !important;
    right: 166px !important;
  }
  :deep(.ui_city_change_top .ui_city_change_inner, .ui_city_change_bottom .ui_city_change_inner) {
    box-shadow: none;
    height: 34px;
    line-height: 34px;
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
  }

  .list_wrapper {
    position: absolute;
    top: 60px;
    bottom: 16px;
    right: 16px;
    z-index: 99;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #fff;
    border-radius: 3px;
    padding: 16px;
    width: 214px;
    overflow-y: auto;

    .content_wrapper {
      width: 100%;
      height: 100%;
      .l_item {
        color: #666666;
        border-bottom: 1px solid #eee;
        padding-bottom: 8px;
        cursor: pointer;
        &:not(:last-child) {
          margin-bottom: 12px;
        }
        .l_title {
          font-weight: bold;
          margin-bottom: 8px;
        }
        .l_address {
        }
      }
    }

    .empty_text {
      color: #999999;
    }
  }

  .search_input {
    position: absolute;
    top: 16px;
    right: 16px;
    width: 150px;
    z-index: 99;
    --n-border: none !important;
    --n-border-hover: 0 solid #157bef !important;
    --n-border-focus: 0 solid #157bef !important;
    --n-box-shadow-focus: none !important;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;

    &::before {
      content: '';
      display: inline-block;
      width: 2px;
      height: 20px;
      background: #eee;
      position: relative;
      left: -1px;
      top: 7px;
    }
  }

  .container {
    width: 100%;
    height: 100%;
  }
  .btn_box {
    position: absolute;
    left: 24px;
    top: 24px;
    z-index: 999;

    .btn {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      width: 60px;
      height: 60px;
      font-size: 12px;
      background: #fff;
      cursor: pointer;

      &:first-child,
      &:last-child {
        border-radius: 4px;
      }
      &:not(:last-child) {
        border-bottom: 1px solid #eee;
      }
      &:first-child {
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
      }
      &:last-child {
        border-top-left-radius: 0;
        border-top-right-radius: 0;
      }
    }

    .active_btn {
      background: #0779ff;
      color: #fff;
    }
  }
  .expend_btn {
    position: absolute;
    right: 0;
    top: calc(50% - 20px);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 40px;
    background: #fff;
    border-radius: 4px;
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
    z-index: 999;
    cursor: pointer;
  }
}
</style>
